#ifndef MESH_H
#define MESH_H

#include "shader.h"
#include "includes/assimp/Importer.hpp"

#include <string>
#include <fstream>
#include <sstream>
#include <iostream>
#include <vector>
using namespace std;

struct Texture {
    unsigned int id;
    string type;
    aiString path;
};

struct Vertex {
    // position
    QVector3D Position;
    unsigned int id;
    vector<unsigned int> adjVertices;
    // normal
    QVector3D Normal;
    // texCoords
    QVector2D TexCoords;
    // tangent
    QVector3D Tangent;
    // bitangent
    QVector3D Bitangent;

    Vertex() {
        Position = QVector3D(0.0f, 0.0f, 0.0f);
    }

    void putAdj(unsigned int v) {
        for (unsigned int i = 0; i < adjVertices.size(); ++i) {
            if (v == adjVertices[i]) return ;
        }
        adjVertices.push_back(v);
    }
};

struct Edge {
    vector<unsigned int> vertices; // size: 2
    vector<unsigned int> faces;    // size: 2
    int newVertexIndice;
    Edge() {
        // >= 0 means gen a new edge vertex
        newVertexIndice = -1;
    }
    Edge(vector<unsigned int> vert) {
        newVertexIndice = -1;
        vertices = vert;
    }
    bool is(const Edge& e) {
        return ((e.vertices[0] == vertices[0] && e.vertices[1] == vertices[1] ) ||
                (e.vertices[1] == vertices[0] && e.vertices[0] == vertices[1]));
    }
};

struct Face {
    vector<unsigned int> vertices; // size: 4
    vector<unsigned int> edges;    // size: 4
    Face() {
        vertices.clear();
        edges.clear();
    }
    Face(vector<unsigned int> vert) {
        vertices = vert;
    }
    Face(const Face& f) {
        this->vertices = f.vertices;
        this->edges = f.edges;
    }
    bool is(const Face& f) {
        for (unsigned int i = 0; i < vertices.size(); ++i) {
            if (vertices[i] != f.vertices[i]) return false;
        }
        return true;
    }
};

struct Mesh {
    /*  Mesh Data  */
    int clarkLoop; // 1 : clark; 2 : loop
    vector<Vertex> vertices;
    vector<Face> faces;
    vector<Edge> edges;
    vector<unsigned int> indices;
    vector<Texture> textures;
    unsigned int VAO;
    bool hasVAO;
    vector<Vertex> verticesWithLight;
    vector<unsigned int> indicesWithLight;
    vector<Vertex> originalVertices;
    vector<unsigned int> originalIndices;
    int subdivisionStep;
    int maxStep;

    /*  Render data  */
    unsigned int VBO, EBO;

    Mesh(vector<Vertex> vertices, vector<unsigned int> indices, vector<Texture> textures)
    {
        this->vertices = vertices;
        this->indices = indices;
        this->textures = textures;
        hasVAO = false;

        subdivisionStep = 0;
        if (vertices.size() > 24) maxStep = 4;
        else if (vertices.size() > 10) maxStep = 5;
        else maxStep = 6;

        for (unsigned int i = 0; i < vertices.size(); ++i)
            this->vertices[i].id = i;

        originalVertices = this->vertices;
        originalIndices = this->indices;
    }

    void originalMesh() {
        subdivisionStep = 0;
        vertices = originalVertices;
        indices = originalIndices;
    }

    void clarkSubdivision() {
        //maxStep = 5;
        subdivisionStep ++;
        if (subdivisionStep > maxStep) return ;
        setFace();
        setEdge();
        setAdjVertex();
        indices.clear();
        vector<Vertex> oldVertices = vertices;
        unsigned int oldVerticesNum = vertices.size(); //qDebug() << vertices.size() << indices.size();
        for (unsigned int i = 0; i < oldVerticesNum; ++i) {
            // update old vertex data
            //float gamma = 0.33;
            float beta = 0.5;
            float alpha = 0.5;

            QVector3D comp1(0.0f, 0.0f, 0.0f), comp2(0.0f, 0.0f, 0.0f), comp3(0.0f, 0.0f, 0.0f);
            comp1 = alpha * oldVertices[i].Position;
//            for (unsigned int j = 0; j < oldVertices[i].adjVertices.size(); ++j) {
//                if (j % 2 == 0)
//                    comp2 += oldVertices[oldVertices[i].adjVertices[j]].Position;
//                else
//                    comp3 += oldVertices[oldVertices[i].adjVertices[j]].Position;
//            }
//            if (oldVertices[i].adjVertices.size() % 2 == 0) {
//                comp2 *= beta / oldVertices[i].adjVertices.size()/2;
//                comp3 *= gamma / oldVertices[i].adjVertices.size()/2;
//            } else {
//                comp2 *= beta / (oldVertices[i].adjVertices.size()/2 + 1);
//                comp3 *= gamma / oldVertices[i].adjVertices.size()/2;
//            }
            for (unsigned int j = 0; j < oldVertices[i].adjVertices.size(); ++j) {
                comp2 += oldVertices[oldVertices[i].adjVertices[j]].Position;
            }
            comp2 *= beta / oldVertices[i].adjVertices.size();
            vertices[i].Position = comp1 + comp2 + comp3;
        } //qDebug() << vertices.size() << indices.size();
        for (unsigned int i = 0; i < faces.size(); ++i) {
            Vertex v1 = oldVertices[faces[i].vertices[0]];
            Vertex v2 = oldVertices[faces[i].vertices[1]];
            Vertex v3 = oldVertices[faces[i].vertices[2]];
            Vertex v4 = oldVertices[faces[i].vertices[3]];
            // set F Vertex data
            Vertex newFaceVertex;
            newFaceVertex.Position = 1.0f/4 * (v1.Position + v2.Position + v3.Position + v4.Position);
            newFaceVertex.id = vertices.size();
            vertices.push_back(newFaceVertex);

            Face f = faces[i];
            // set first E vertex data
            Vertex newEdgeVertex1;
            if (edges[f.edges[0]].newVertexIndice >= 0) {
                newEdgeVertex1.id = edges[f.edges[0]].newVertexIndice;
            } else {
                newEdgeVertex1.Position = 3.0/8 * (oldVertices[edges[f.edges[0]].vertices[0]].Position + oldVertices[edges[f.edges[0]].vertices[1]].Position);
                newEdgeVertex1.Position += 1.0/16 * (oldVertices[edges[f.edges[2]].vertices[0]].Position + oldVertices[edges[f.edges[2]].vertices[1]].Position);
                Face adjF;
                //if (f.is(faces[edges[f.edges[0]].faces[0]]))
                if (i == edges[f.edges[0]].faces[0])
                    adjF = faces[edges[f.edges[0]].faces[1]];
                else
                    adjF = faces[edges[f.edges[0]].faces[0]];
                QVector3D t(0.0f, 0.0f, 0.0f);
                for (unsigned int j = 0; j < 4; ++j) {
                    if (adjF.vertices[j] != edges[f.edges[0]].vertices[0] && adjF.vertices[j] != edges[f.edges[0]].vertices[1])
                        t += oldVertices[adjF.vertices[j]].Position;
                }
                newEdgeVertex1.Position += 1.0/16 * t;
                newEdgeVertex1.id = vertices.size();
                edges[f.edges[0]].newVertexIndice = newEdgeVertex1.id;
                vertices.push_back(newEdgeVertex1);
            }
            // set second E vertex data
            Vertex newEdgeVertex2;
            if (edges[f.edges[1]].newVertexIndice >= 0) {
                newEdgeVertex2.id = edges[f.edges[1]].newVertexIndice;
            } else {
                newEdgeVertex2.Position = 3.0/8 * (oldVertices[edges[f.edges[1]].vertices[0]].Position + oldVertices[edges[f.edges[1]].vertices[1]].Position);
                newEdgeVertex2.Position += 1.0/16 * (oldVertices[edges[f.edges[3]].vertices[0]].Position + oldVertices[edges[f.edges[3]].vertices[1]].Position);
                Face adjF;
                //if (f.is(faces[edges[f.edges[1]].faces[0]]))
                if (i == edges[f.edges[1]].faces[0])
                    adjF = faces[edges[f.edges[1]].faces[1]];
                else
                    adjF = faces[edges[f.edges[1]].faces[0]];
                QVector3D t = QVector3D(0.0f, 0.0f, 0.0f);
                for (unsigned int j = 0; j < 4; ++j) {
                    if (adjF.vertices[j] != edges[f.edges[1]].vertices[0] && adjF.vertices[j] != edges[f.edges[1]].vertices[1])
                        t += oldVertices[adjF.vertices[j]].Position;
                }
                newEdgeVertex2.Position += 1.0/16 * t;
                newEdgeVertex2.id = vertices.size();
                edges[f.edges[1]].newVertexIndice = newEdgeVertex2.id;
                vertices.push_back(newEdgeVertex2);
            }
            // set third E vertex data
            Vertex newEdgeVertex3;
            if (edges[f.edges[2]].newVertexIndice >= 0) {
                newEdgeVertex3.id = edges[f.edges[2]].newVertexIndice;
            } else {
                newEdgeVertex3.Position = 3.0/8 * (oldVertices[edges[f.edges[2]].vertices[0]].Position + oldVertices[edges[f.edges[2]].vertices[1]].Position);
                newEdgeVertex3.Position += 1.0/16 * (oldVertices[edges[f.edges[0]].vertices[0]].Position + oldVertices[edges[f.edges[0]].vertices[1]].Position);
                Face adjF;
                //if (f.is(faces[edges[f.edges[2]].faces[0]]))
                if (i == edges[f.edges[2]].faces[0])
                    adjF = faces[edges[f.edges[2]].faces[1]];
                else
                    adjF = faces[edges[f.edges[2]].faces[0]];
                QVector3D t = QVector3D(0.0f, 0.0f, 0.0f);
                for (unsigned int j = 0; j < 4; ++j) {
                    if (adjF.vertices[j] != edges[f.edges[2]].vertices[0] && adjF.vertices[j] != edges[f.edges[2]].vertices[1])
                        t += oldVertices[adjF.vertices[j]].Position;
                }
                newEdgeVertex3.Position += 1.0/16 * t;
                newEdgeVertex3.id = vertices.size();
                edges[f.edges[2]].newVertexIndice = newEdgeVertex3.id;
                vertices.push_back(newEdgeVertex3);
            }
            // set fourth E vertex data
            Vertex newEdgeVertex4;
            if (edges[f.edges[3]].newVertexIndice >= 0) {
                newEdgeVertex4.id = edges[f.edges[3]].newVertexIndice;
            } else {
                newEdgeVertex4.Position = 3.0/8 * (oldVertices[edges[f.edges[3]].vertices[0]].Position + oldVertices[edges[f.edges[3]].vertices[1]].Position);
                newEdgeVertex4.Position += 1.0/16 * (oldVertices[edges[f.edges[1]].vertices[0]].Position + oldVertices[edges[f.edges[1]].vertices[1]].Position);
                Face adjF;
                //if (f.is(faces[edges[f.edges[3]].faces[0]]))
                if (i == edges[f.edges[3]].faces[0])
                    adjF = faces[edges[f.edges[3]].faces[1]];
                else
                    adjF = faces[edges[f.edges[3]].faces[0]];
                QVector3D t = QVector3D(0.0f, 0.0f, 0.0f);
                for (unsigned int j = 0; j < 4; ++j) {
                    if (adjF.vertices[j] != edges[f.edges[3]].vertices[0] && adjF.vertices[j] != edges[f.edges[3]].vertices[1])
                        t += oldVertices[adjF.vertices[j]].Position;
                }
                newEdgeVertex4.Position += 1.0/16 * t;
                newEdgeVertex4.id = vertices.size();
                edges[f.edges[3]].newVertexIndice = newEdgeVertex4.id;
                vertices.push_back(newEdgeVertex4);
            }

            // update indices
            // first new face
            indices.push_back(newFaceVertex.id);
            indices.push_back(newEdgeVertex1.id);
            indices.push_back(v2.id);
            indices.push_back(newEdgeVertex2.id);
            //qDebug() << newFaceVertex.id << newEdgeVertex1.id << v2.id << newEdgeVertex2.id; if (indices.size() >= 4) break;
            // second new face
            indices.push_back(newFaceVertex.id);
            indices.push_back(newEdgeVertex2.id);
            indices.push_back(v3.id);
            indices.push_back(newEdgeVertex3.id);
            // third new face
            indices.push_back(newFaceVertex.id);
            indices.push_back(newEdgeVertex3.id);
            indices.push_back(v4.id);
            indices.push_back(newEdgeVertex4.id);
            // fourth new face
            indices.push_back(newFaceVertex.id);
            indices.push_back(newEdgeVertex4.id);
            indices.push_back(v1.id);
            indices.push_back(newEdgeVertex1.id);
        } //qDebug() << vertices.size() << indices.size();
    }

    void loopSubdivision() {
        //maxStep = 5;
        subdivisionStep ++;
        if (subdivisionStep > maxStep) return ;
        setFace();
        setEdge();
        setAdjVertex();
        indices.clear();
        vector<Vertex> oldVertices = vertices;
        unsigned int oldVerticesNum = vertices.size(); //qDebug() << vertices.size() << indices.size();
        for (unsigned int i = 0; i < oldVerticesNum; ++i) {
            // update old vertex data
            float PI = 3.141592653;
            int n = oldVertices[i].adjVertices.size();
            float beta = 1.0/n * (5.0/8 - (3.0/8 + 1.0/4 * cos(2*PI/n)) * (3.0/8 + 1.0/4 * cos(2*PI/n)));

            QVector3D comp1(0.0f, 0.0f, 0.0f), comp2(0.0f, 0.0f, 0.0f);
            comp1 = (1 - n * beta) * oldVertices[i].Position;
            for (unsigned int j = 0; j < oldVertices[i].adjVertices.size(); ++j) {
                comp2 += oldVertices[oldVertices[i].adjVertices[j]].Position;
            }
            comp2 *= beta;
            vertices[i].Position = comp1 + comp2;
        } //qDebug() << vertices.size() << indices.size() << faces.size();
        for (unsigned int i = 0; i < faces.size(); ++i) {
            Vertex v1 = oldVertices[faces[i].vertices[0]];
            Vertex v2 = oldVertices[faces[i].vertices[1]];
            Vertex v3 = oldVertices[faces[i].vertices[2]];

            Face f = faces[i];
            // set first E vertex data
            Vertex newEdgeVertex1;
            if (edges[f.edges[0]].newVertexIndice >= 0) {
                newEdgeVertex1.id = edges[f.edges[0]].newVertexIndice;
            } else {
                newEdgeVertex1.Position = 3.0/8 * (oldVertices[edges[f.edges[0]].vertices[0]].Position + oldVertices[edges[f.edges[0]].vertices[1]].Position);
                newEdgeVertex1.Position += 1.0/8 * oldVertices[f.vertices[2]].Position;
                Face adjF;
                if (i == edges[f.edges[0]].faces[0])
                    adjF = faces[edges[f.edges[0]].faces[1]];
                else
                    adjF = faces[edges[f.edges[0]].faces[0]];
                QVector3D t(0.0f, 0.0f, 0.0f);
                for (unsigned int j = 0; j < 3; ++j) {
                    if (adjF.vertices[j] != edges[f.edges[0]].vertices[0] && adjF.vertices[j] != edges[f.edges[0]].vertices[1])
                        t += oldVertices[adjF.vertices[j]].Position;
                }
                newEdgeVertex1.Position += 1.0/8 * t;
                newEdgeVertex1.id = vertices.size();
                edges[f.edges[0]].newVertexIndice = newEdgeVertex1.id;
                vertices.push_back(newEdgeVertex1);
            }
            // set second E vertex data
            Vertex newEdgeVertex2;
            if (edges[f.edges[1]].newVertexIndice >= 0) {
                newEdgeVertex2.id = edges[f.edges[1]].newVertexIndice;
            } else {
                newEdgeVertex2.Position = 3.0/8 * (oldVertices[edges[f.edges[1]].vertices[0]].Position + oldVertices[edges[f.edges[1]].vertices[1]].Position);
                newEdgeVertex2.Position += 1.0/8 * oldVertices[f.vertices[0]].Position;
                Face adjF;
                if (i == edges[f.edges[1]].faces[0])
                    adjF = faces[edges[f.edges[1]].faces[1]];
                else
                    adjF = faces[edges[f.edges[1]].faces[0]];
                QVector3D t(0.0f, 0.0f, 0.0f);
                for (unsigned int j = 0; j < 3; ++j) {
                    if (adjF.vertices[j] != edges[f.edges[1]].vertices[0] && adjF.vertices[j] != edges[f.edges[1]].vertices[1])
                        t += oldVertices[adjF.vertices[j]].Position;
                }
                newEdgeVertex2.Position += 1.0/8 * t;
                newEdgeVertex2.id = vertices.size();
                edges[f.edges[1]].newVertexIndice = newEdgeVertex2.id;
                vertices.push_back(newEdgeVertex2);
            }
            // set third E vertex data
            Vertex newEdgeVertex3;
            if (edges[f.edges[2]].newVertexIndice >= 0) {
                newEdgeVertex3.id = edges[f.edges[2]].newVertexIndice;
            } else {
                newEdgeVertex3.Position = 3.0/8 * (oldVertices[edges[f.edges[2]].vertices[0]].Position + oldVertices[edges[f.edges[2]].vertices[1]].Position);
                newEdgeVertex3.Position += 1.0/8 * oldVertices[f.vertices[1]].Position;
                Face adjF;
                if (i == edges[f.edges[2]].faces[0])
                    adjF = faces[edges[f.edges[2]].faces[1]];
                else
                    adjF = faces[edges[f.edges[2]].faces[0]];
                QVector3D t(0.0f, 0.0f, 0.0f);
                for (unsigned int j = 0; j < 3; ++j) {
                    if (adjF.vertices[j] != edges[f.edges[2]].vertices[0] && adjF.vertices[j] != edges[f.edges[2]].vertices[1])
                        t += oldVertices[adjF.vertices[j]].Position;
                }
                newEdgeVertex3.Position += 1.0/8 * t;
                newEdgeVertex3.id = vertices.size();
                edges[f.edges[2]].newVertexIndice = newEdgeVertex3.id;
                vertices.push_back(newEdgeVertex3);
            }

            // update indices
            // first new face
            indices.push_back(v1.id);
            indices.push_back(newEdgeVertex1.id);
            indices.push_back(newEdgeVertex3.id);
            // second new face
            indices.push_back(newEdgeVertex1.id);
            indices.push_back(v2.id);
            indices.push_back(newEdgeVertex2.id);
            // third new face
            indices.push_back(newEdgeVertex2.id);
            indices.push_back(v3.id);
            indices.push_back(newEdgeVertex3.id);
            // fourth new face
            indices.push_back(newEdgeVertex1.id);
            indices.push_back(newEdgeVertex2.id);
            indices.push_back(newEdgeVertex3.id); //if (indices.size() >= 36) break;
        } //qDebug() << vertices.size() << indices.size();
    }

    void setFace() {
        //qDebug() << indices.size() << clarkLoop;
        faces.clear();
        if (clarkLoop == 1) {
            for (unsigned int i = 0; i < indices.size(); i += 4) {
                vector<unsigned int> t;
                t.push_back(indices[i]);
                t.push_back(indices[i + 1]);
                t.push_back(indices[i + 2]);
                t.push_back(indices[i + 3]);
                Face newFace(t);
                faces.push_back(newFace);
                //qDebug() << t[0] << t[1] << t[2] << t[3];
            }
        } else if (clarkLoop == 2) {
            for (unsigned int i = 0; i < indices.size(); i += 3) {
                vector<unsigned int> t;
                t.push_back(indices[i]);
                t.push_back(indices[i + 1]);
                t.push_back(indices[i + 2]);
                Face newFace(t);
                faces.push_back(newFace);
            }
        }
        //qDebug() << faces.size();
    }

    void genNormal() { //qDebug() << vertices.size() << indices.size();
        verticesWithLight.clear();
        indicesWithLight.clear();
        for (unsigned int i = 0; i < indices.size(); ++i) {
            verticesWithLight.push_back(vertices[indices[i]]);
            verticesWithLight[i].id = i;
            indicesWithLight.push_back(i);
        } //qDebug() << verticesWithLight.size() << indicesWithLight.size();
        if (clarkLoop == 1) { //qDebug() << verticesWithLight.size() << indicesWithLight.size();
            for (unsigned int i = 0; i < verticesWithLight.size(); i += 4) {
                QVector3D v1 = verticesWithLight[i + 1].Position - verticesWithLight[i].Position;
                QVector3D v2 = verticesWithLight[i + 3].Position - verticesWithLight[i].Position;
                QVector3D normal = QVector3D::crossProduct(v1, v2).normalized();
                verticesWithLight[i + 0].Normal += normal;
                verticesWithLight[i + 1].Normal += normal;
                verticesWithLight[i + 2].Normal += normal;
                verticesWithLight[i + 3].Normal += normal;
            }
        } else if (clarkLoop == 2) {
            for (unsigned int i = 0; i < verticesWithLight.size(); i += 3) {
                QVector3D v1 = verticesWithLight[i + 1].Position - verticesWithLight[i].Position;
                QVector3D v2 = verticesWithLight[i + 2].Position - verticesWithLight[i].Position;
                QVector3D normal = QVector3D::crossProduct(v1, v2).normalized();
                verticesWithLight[i + 0].Normal += normal;
                verticesWithLight[i + 1].Normal += normal;
                verticesWithLight[i + 2].Normal += normal;
            }
        }
    }

    unsigned int putEdge(Edge e, unsigned int faceId) {
        for (unsigned int i = 0; i < edges.size(); ++i) {
            if ((edges[i].vertices[0] == e.vertices[0] && edges[i].vertices[1] == e.vertices[1]) ||
                    (edges[i].vertices[0] == e.vertices[1] && edges[i].vertices[1] == e.vertices[0])) {
                edges[i].faces.push_back(faceId);
                return i;
            }
        }
        e.faces.push_back(faceId);
        edges.push_back(e);
        return edges.size() - 1;
    }

    void setEdge() {
        edges.clear();
        //qDebug() << faces.size();
        if (clarkLoop == 1) {
            for (unsigned int i = 0; i < faces.size(); ++i) {
                vector<unsigned int> f = faces[i].vertices;
                vector<unsigned int> e;
                e.clear();
                e.push_back(f[0]);
                e.push_back(f[1]);
                faces[i].edges.push_back(putEdge(e, i));
                e.clear();
                e.push_back(f[1]);
                e.push_back(f[2]);
                faces[i].edges.push_back(putEdge(e, i));
                e.clear();
                e.push_back(f[2]);
                e.push_back(f[3]);
                faces[i].edges.push_back(putEdge(e, i));
                e.clear();
                e.push_back(f[3]);
                e.push_back(f[0]);
                faces[i].edges.push_back(putEdge(e, i));
                //qDebug() << edges.size() << f[0] << f[1] << f[2] << f[3];
            }
        } else if (clarkLoop == 2) {
            for (unsigned int i = 0; i < faces.size(); ++i) {
                vector<unsigned int> f = faces[i].vertices;
                vector<unsigned int> e;
                e.clear();
                e.push_back(f[0]);
                e.push_back(f[1]);
                faces[i].edges.push_back(putEdge(e, i));
                //edges.push_back(Edge(e));
                e.clear();
                e.push_back(f[1]);
                e.push_back(f[2]);
                faces[i].edges.push_back(putEdge(e, i));
                //edges.push_back(Edge(e));
                e.clear();
                e.push_back(f[2]);
                e.push_back(f[0]);
                faces[i].edges.push_back(putEdge(e, i));
                //edges.push_back(Edge(e));
            }
        }
        //qDebug() << edges.size();
    }

    void setAdjVertex() {
        for (unsigned int i = 0; i < vertices.size(); ++i) {
            vertices[i].adjVertices.clear();
        }
        for (unsigned int i = 0; i < edges.size(); ++i) {
            vector<unsigned int> e = edges[i].vertices;
            vertices[e[0]].putAdj(e[1]);
            //vertices[e[0]].adjVertices.push_back(e[1]);
            vertices[e[1]].putAdj(e[0]);
            //vertices[e[1]].adjVertices.push_back(e[0]);
        }
        // check
        int numOfEdges = 0;
        for (unsigned int i = 0; i < vertices.size(); ++i) {
            numOfEdges += vertices[i].adjVertices.size();
            //qDebug() << vertices[i].adjVertices.size();
        } //qDebug() << "double edges : " << numOfEdges;
    }
};


#endif // MESH_H
